#include <cstdio>
#include <cstring>
#include <map>
#include <string>

#include "pmu.h"       // libkperf
#include "pcerrc.h"    // libkperf
#include "symbol.h"    // libkperf

#define DUMPLOG "/data/pmudata.log"

int pd = 0;
std::map<std::string, std::map<unsigned long, int>> profile;

int get_target_pid ()
{
    int pid = -1;
    char buffer[1024] = "pidof mysqld";
    FILE *fp = popen(buffer, "r");
    if (fp == NULL) {
        return -1;
    }

    if (fgets(buffer, sizeof(buffer), fp) != NULL) {
        pclose(fp);
        pid = atoi(buffer);
    }
    return pid;
}

void start_kperf()
{
    char *evtList[1] = {"cycles"};
    int pids[1] = {get_target_pid()};
    struct PmuAttr attr = {0};
    attr.evtList = evtList;
    attr.numEvt = 1;
    attr.pidList = pids;
    attr.numPid = 1;
    attr.symbolMode = RESOLVE_ELF;
    attr.freq = 2000;
    attr.useFreq = 1;
    attr.excludeKernel = 1;
    attr.includeNewFork = 1;

    pd = PmuOpen(SAMPLING, &attr);
    PmuEnable(pd);
}

void stop_kperf()
{
    PmuClose(pd);
}

void update_app_profile_data(char* funcname, unsigned long offset)
{
    // printf("funcname: %s, offset: %lu\n", funcname, offset);
    if (profile.find(funcname) != profile.end()) {
        if (profile[funcname].find(offset) != profile[funcname].end()) {
            profile[funcname][offset]++;
        } else {
            profile[funcname][offset] = 1;
        }
    } else {
        profile[funcname] = std::map<unsigned long, int>();
        profile[funcname][offset] = 1;
    }
}

void process_data_inner(struct PmuData *data, int len)
{
    for (int i = 0; i < len; i++) {
        if (std::string(data[i].comm) != "mysqld") {
            continue;
        }
        auto sym = data[i].stack->symbol;
        if (!sym) {
            continue;
        }

        char *funcname = sym->mangleName;
        // char *funcname = sym->symbolName;
        unsigned long offset = sym->offset;
        // DEBUG("func: " << funcname << ", offset: " << offset);
        
        if (funcname == nullptr || strcmp(funcname, "UNKNOWN") == 0) {
            continue;
        }

        update_app_profile_data(funcname, offset);
    }

    int cnt = 0;
    for (auto pair : profile) {
        cnt += pair.second.size();
        printf("profile func: [%s], offsets: %d\n", pair.first.c_str(), pair.second.size());
    }
    printf("[process_data] current profile line size: %d\n", cnt);
}

void process_data()
{
    struct PmuData *data;
    int len;
    static bool processing = false;

    if (processing) {
        return;
    }
    processing = true;
    PmuDisable(pd);
    len = PmuRead(pd, &data);
    // PmuDataFree(data);
    printf("[process_data] new data len: %d\n", len);
    PmuDumpData(data, len, DUMPLOG, 0);
    PmuEnable(pd);
    processing = false;

    // process_data_inner(data, len);
}

int main()
{
    std::remove(DUMPLOG);

    start_kperf();
    if (pd < 0) {
        printf("start_kperf failed, pd: %d\n", pd);
        return -1;
    } else {
        printf("start_kperf success, pd: %d\n", pd);
    }

    while (true) {
        sleep(1);
        process_data();
    }

    stop_kperf();
}
